var _global = require('global/global.js');
var R = require('res/texture.js').R;

var _Cell = require('scenes/game/cell.js');
var _Slot = require('./slot.js');
var _Snap = require('./slotsnap.js');

var MOVE_LEFT = 0;
var MOVE_TOP = 1;
var MOVE_RIGHT = 2;
var MOVE_BOTTOM = 3;
var MOVE_DIRECTIONS = 4;

var _Status = require('./slotstatus.js');
var SLOT_BLOCK = _Status.SLOT_BLOCK;
var SLOT_OVER = _Status.SLOT_OVER;// over an empty cell
var SLOT_MERGE = _Status.SLOT_MERGE;// will merge after movement

var ROW_COUNT = 4, COL_COUNT = 4;
var CELLS_COUNT = ROW_COUNT * COL_COUNT;

// ==========================
// CellItor
// ==========================
/**
 * @param group all cells
 * @param first
 * @param linecount lines
 * @param linestride
 * @param slotscount cells of each line
 * @param slotstride
 * @constructor
 */
function CellItor(group, first, linecount, linestride, slotscount, slotstride) {
    this.mGroup = group;
    this.mFirst = first;
    this.mLines = linecount;
    this.mLineStride = linestride;
    this.mLength = slotscount;
    this.mCellStride = slotstride;

    this.mLineIndex = 0;
    this.mCellIndex = 0;
}
CellItor.prototype.start = function () {
    this.mLineIndex = -1;
    this.mCellIndex = -1;
    return this;
};
CellItor.prototype.nextLine = function () {
    this.mCellIndex = -1;
    return (++this.mLineIndex) < this.mLines;
};
CellItor.prototype.hasNext = function () {
    return this.mCellIndex < this.mLength - 1;
};
CellItor.prototype.next = function () {
    this.mCellIndex++;
    return this.mGroup[this.mFirst + this.mLineIndex * this.mLineStride + this.mCellIndex * this.mCellStride];
};
CellItor.prototype.last = function () {
    return this.mGroup[this.mFirst + this.mLineIndex * this.mLineStride + (this.mLength - 1) * this.mCellStride];
};
CellItor.prototype.getCell = function (line, index) {
    return this.mGroup[this.mFirst + line * this.mLineStride + index * this.mCellStride];
};
/**
 * return last cell of a line
 * @param line
 * @returns {*}
 */
CellItor.prototype.getLastCell = function (line) {
    return this.mGroup[this.mFirst + line * this.mLineStride + (this.mLength - 1) * this.mCellStride];
};

// ==========================
// Data
// ==========================
/**
 * @constructor
 */
function MoveSnap() {
    this.mData = new Array(MOVE_DIRECTIONS);
    this.mData[MOVE_LEFT] = new _Snap(ROW_COUNT);
    this.mData[MOVE_RIGHT] = new _Snap(ROW_COUNT);
    this.mData[MOVE_TOP] = new _Snap(COL_COUNT);
    this.mData[MOVE_BOTTOM] = new _Snap(COL_COUNT);
}
MoveSnap.prototype.getSnap = function (direction) {
    return this.mData[direction];
};

function MergeCount() {
    this.mX = new Array(COL_COUNT);
    this.mY = new Array(ROW_COUNT);
    this.mCount = 0;
    this.reset();
}
MergeCount.prototype.reset = function () {
    this.mCount = 0;
    for (var i = 0; i < COL_COUNT; i++) {
        this.mX[i] = 0;
    }
    for (var i = 0; i < ROW_COUNT; i++) {
        this.mY[i] = 0;
    }
};
MergeCount.prototype.getMergeCount = function () {
    return this.mCount;
};
MergeCount.prototype.getLines = function (direction) {
    if (direction == MOVE_LEFT || direction == MOVE_RIGHT) {
        return this.mY;
    }
    return this.mX;
};
MergeCount.prototype.addMerge = function (slot) {
    var index = slot.getIndex();
    var col = index % COL_COUNT;
    var row = (index - col ) / ROW_COUNT;
    this.mCount++;
    this.mX[col]++;
    this.mY[row]++;
};
MergeCount.prototype.toString = function () {
    var str = this.mX.join(',');
    for (var i = 0; i < ROW_COUNT; i++) {
        str += '\n' + this.mY[i];
    }
    return str;
};

function MergeHistory() {
    this.mSnaps = [];
    for (var i = 0; i < 2; i++) {
        this.mSnaps.push(new MergeCount());
    }
    this.mIndex = 1;
}
MergeHistory.prototype.moveNext = function () {
    this.mIndex = (this.mIndex + 1) % this.mSnaps.length;
};
MergeHistory.prototype.getCurrent = function () {
    return this.mSnaps[this.mIndex];
};
MergeHistory.prototype.getPrevious = function () {
    return this.mSnaps[(this.mIndex + this.mSnaps.length - 1) % this.mSnaps.length];
};

// ==========================
// CellGroup
// ==========================
function CellGroup() {
    this.mSlots = [];
    this.mCellBgs = [];
    for (var i = 0; i < CELLS_COUNT; i++) {
        var slot = new _Slot(i);
        this.mSlots.push(slot);

        var bg = _global.spriteNode(R.cards.card_bg);
        bg.setAnchor(0.5, 0.5);
        bg.setPosition(slot.getPosition());
        this.mCellBgs.push(bg);
    }

    this.mTotalMove = 0;
    this.mTotalSlot = 0;
    this.mMaxSlot = 3;// max card number at this moment
    this.mMaxCount = 0;

    this.mMoveSlots = new Array(MOVE_DIRECTIONS);
    this.mMergeSlots = new Array(MOVE_DIRECTIONS);

    this.mMoveSnap = new MoveSnap();
    this.mMergeHis = new MergeHistory();

    this.mItors = new Array(MOVE_DIRECTIONS);
    this.mItors[MOVE_LEFT] = new CellItor(this.mSlots, 0, ROW_COUNT, COL_COUNT, COL_COUNT, 1);
    this.mItors[MOVE_RIGHT] = new CellItor(this.mSlots, COL_COUNT - 1, ROW_COUNT, COL_COUNT, COL_COUNT, -1);
    this.mItors[MOVE_TOP] = new CellItor(this.mSlots, 0, COL_COUNT, 1, ROW_COUNT, COL_COUNT);
    this.mItors[MOVE_BOTTOM] = new CellItor(this.mSlots, CELLS_COUNT - COL_COUNT, COL_COUNT, 1, ROW_COUNT, -COL_COUNT);
}
CellGroup.prototype.getSlotRowCol = function (r, c) {
    return this.mItors[MOVE_LEFT].getCell(r, c);
};
CellGroup.prototype.getSlot = function (index) {
    return this.mSlots[index];
};
CellGroup.prototype.getAllSlots = function () {
    return this.mSlots;
};
CellGroup.prototype.getLineEnd = function (direction, line) {
    return this.mItors[direction].getLastCell(line);
};
CellGroup.prototype.getMoveSnap = function (direction) {
    return this.mMoveSnap.getSnap(direction);
};
CellGroup.prototype.getMergeHis = function () {
    return this.mMergeHis;
};
/**
 * @param direction
 */
CellGroup.prototype.predictMerge = function (direction) {
    this.mMergeHis.moveNext();
    var mcount = this.mMergeHis.getCurrent();
    mcount.reset();

    var itor = this.mItors[direction].start();
    while (itor.nextLine()) {
        var previous = null;

        while (itor.hasNext()) {
            var slot = itor.next();
            if (slot.getPredict(direction) == SLOT_MERGE) {
                mcount.addMerge(previous);// count merge
            }
            previous = slot;
        }
    }
};
CellGroup.prototype.startItor = function (predict) {
    return this.mItors[predict].start();
};
CellGroup.prototype.isBlock = function (predict) {
    return this.mMoveSlots[predict] == 0;
};
/**
 * @param predict move direction
 * @param vector
 */
CellGroup.prototype.move = function (predict, vector) {
    for (var i = 0; i < CELLS_COUNT; i++) {
        this.mSlots[i].moveCell(predict, vector);
    }
};
CellGroup.prototype.draw = function (context) {
    for (var i = 0; i < CELLS_COUNT; i++) {
        this.mCellBgs[i].draw(context);
    }
    for (var i = 0; i < CELLS_COUNT; i++) {
        this.mSlots[i].draw(context);
    }
};
CellGroup.prototype.recoverFromLostContext = function (context) {
    for (var i = 0; i < CELLS_COUNT; i++) {
        this.mCellBgs[i].recoverFromLostContext(context);
        this.mSlots[i].recoverFromLostContext(context);
    }
};
CellGroup.prototype.drawMoing = function (direction, context) {
    for (var i = 0; i < CELLS_COUNT; i++) {
        this.mSlots[i].drawMoing(direction, context);
    }
};
/**
 * @param direction
 */
CellGroup.prototype.checkPredict = function (direction) {
    var movecount = 0;// count of slots that can move
    var mergecount = 0;// count of slots that merge
    var total = 0;

    var snap = this.mMoveSnap.getSnap(direction);
    snap.reset();

    var line = 0;
    var itor = this.mItors[direction].start();
    while (itor.nextLine()) {
        var previous = itor.next();
        var predict = SLOT_BLOCK;
        previous.setPredict(direction, predict);
        !previous.isEmpty() && total++;

        var findmerge = false;
        var findover = false;

        while (itor.hasNext()) {
            var next = itor.next();
            predict = next.checkPredict(previous, predict);
            next.setPredict(direction, predict);
            !next.isEmpty() && total++;
            previous = next;

            if (predict == SLOT_MERGE) {
                findmerge = true;
                mergecount++;
            }
            if (predict == SLOT_OVER) {
                findover = true;
            }
            if (predict != SLOT_BLOCK) {
                movecount++;
            }
        }

        if (findmerge) {
            snap.setState(line, SLOT_MERGE);
        } else if (findover) {
            snap.setState(line, SLOT_OVER);
        } else if (previous.isEmpty()) {
            snap.setState(line, -1);
        } else {
            snap.setState(line, SLOT_BLOCK);
        }

        line++;
    }

    this.mTotalSlot = total;
    this.mTotalMove += movecount;
    this.mMoveSlots[direction] = movecount;
    this.mMergeSlots[direction] = mergecount;
};
/**
 * check all possiable movement
 */
CellGroup.prototype.prepareMove = function () {
    this.mTotalSlot = 0;
    this.mTotalMove = 0;
    this.checkPredict(MOVE_LEFT);
    this.checkPredict(MOVE_RIGHT);
    this.checkPredict(MOVE_TOP);
    this.checkPredict(MOVE_BOTTOM);
};
CellGroup.prototype.isNoMove = function () {
    return this.mTotalMove == 0 && this.mTotalSlot == CELLS_COUNT;
};
CellGroup.prototype.getMaxSlot = function () {
    return this.mMaxSlot;
};
CellGroup.prototype.updateMax = function (count) {
    if (count > this.mMaxSlot) {
        this.mMaxSlot = count;
    }
};
/**
 * called by toturial
 * @param direction
 * @param listener {
 *     onMergeFind : function(index, count) {}
 * } will be noticed when each merge occur
 */
CellGroup.prototype.checkMerge = function (direction, listener) {
    var itor = this.mItors[direction].start();
    while (itor.nextLine()) {
        var previous = null;

        while (itor.hasNext()) {
            var slot = itor.next();
            var predict = slot.getPredict(direction);
            if (predict == SLOT_MERGE) {
                listener.onMergeFind(previous.getIndex(), previous.getCell().getCount() + slot.getCell().getCount());
            }

            previous = slot;
        }
    }
};
CellGroup.prototype.finishMerge = function (direction, area) {
    var newtop = 0;
    var top = this.mMaxSlot;

    var itor = this.mItors[direction].start();
    while (itor.nextLine()) {
        var previous = null;

        while (itor.hasNext()) {
            var slot = itor.next();
            var predict = slot.getPredict(direction);
            if (predict == SLOT_MERGE) {
                var mcount = previous.mergeCell(slot);
                this.updateMax(mcount);
                if (mcount >= top) {
                    newtop++;
                }
                area.releaseCell(slot.releaseCell());
            } else if (predict == SLOT_OVER) {
                previous.takeFrom(slot);
            }

            previous = slot;
        }
    }

    if (this.mMaxSlot > 3 && newtop > 0) {
        for (var i = 0; i < CELLS_COUNT; i++) {
            if (this.mSlots[i].isEmpty()) {
                continue;
            }
            this.mSlots[i].getCell().checkTop(this.mMaxSlot);
        }
    }
};
CellGroup.prototype.getCurrentCells = function () {
    var counts = [];
    for (var i = 0; i < CELLS_COUNT; i++) {
        if (!this.mSlots[i].isEmpty()) {
            counts.push(this.mSlots[i].getCell().getCount());
        }
    }
    return counts;
};
/**
 * called at end of the game
 * @returns {Array}
 */
CellGroup.prototype.getCellsCount = function () {
    var counts = [];
    for (var i = 0; i < CELLS_COUNT; i++) {
        counts.push(this.mSlots[i].getCell().getCount());
    }
    return counts;
};

// ================ for test purpose
CellGroup.prototype.loadCellsCount = function (data) {
    for (var i = 0; i < CELLS_COUNT; i++) {
        if (data[i]) {
            this.mSlots[i].bindCell(new _Cell(data[i]));
        } else {
            this.mSlots[i].releaseCell();
        }
    }
};
CellGroup.prototype.toString = function () {
    var str = '';
    for (var d = 0; d < MOVE_DIRECTIONS; d++) {
        str += '----------\n';
        for (var i = 0; i < ROW_COUNT; i++) {
            var line = this.mSlots.slice(i * COL_COUNT, (i + 1) * COL_COUNT);
            for (var l = 0; l < COL_COUNT; l++) {
                str += line[l].toString(d);
            }
            str += '\n';
        }
    }
    return str;
};

module.exports = CellGroup;